#include <iostream>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>

int make_socket_non_blocking (int sfd)
{
  int flags, s;

  flags = fcntl (sfd, F_GETFL, 0);
  if (flags == -1)
  {
    perror ("fcntl");
    return -1;
  }

  flags |= O_NONBLOCK;
  s = fcntl (sfd, F_SETFL, flags);
  if (s == -1)
  {
    perror ("fcntl");
    return -1;
  }

  return 0;
}

int create_and_bind (const char *port)
{
  struct addrinfo hints;
  struct addrinfo *result, *rp;
  int s, sfd;

  memset (&hints, 0, sizeof (struct addrinfo));
  hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
  hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
  hints.ai_flags = AI_PASSIVE;     /* All interfaces */

  s = getaddrinfo (NULL, port, &hints, &result);
  if (s != 0)
  {
    fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));
    return -1;
  }

  for (rp = result; rp != NULL; rp = rp->ai_next)
  {
    sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
    if (sfd == -1)
      continue;

    s = bind (sfd, rp->ai_addr, rp->ai_addrlen);
    if (s == 0)
    {
      /* We managed to bind successfully! */
      break;
    }

    close (sfd);
  }

  if (rp == NULL)
  {
    fprintf (stderr, "Could not bind\n");
    return -1;
  }

  freeaddrinfo (result);

  return sfd;
}

#define MAXEVENTS 1024

int main()
{
  struct epoll_event event;
  struct epoll_event *events;
  int r;
  int sfd = create_and_bind("31337");
  if (sfd == -1)
    return -1;
  r = make_socket_non_blocking(sfd);
  if ( r == -1 )
    return -2;

  r = listen(sfd,SOMAXCONN);
  if ( r == -1)
    return -3;

  int efd = epoll_create1(0);
  if ( efd == -1 )
    return -4;

  event.data.fd = sfd;
  event.events = EPOLLIN | EPOLLET;
  r = epoll_ctl(efd,EPOLL_CTL_ADD,sfd,&event);
  if ( r == -1)
    return -5;

  events = (epoll_event*)calloc (MAXEVENTS, sizeof event);
  
  while ( 1 )
  {
    int n;
    n = epoll_wait(efd,events,MAXEVENTS - 1,-1);
    for (int i = 0; i < n; ++i)
    {
      if ( (events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || ! (events[i].events & EPOLLIN))
      {
        close(events[i].data.fd);
        continue;
      }
      else if (sfd == events[i].data.fd)
      {
        struct sockaddr in_addr;
        socklen_t in_len;
        int s;

        in_len = sizeof in_addr;
        int infd = accept(sfd,&in_addr,&in_len);

        if (infd == -1)
          break;

        s = make_socket_non_blocking(infd);
        if (s == -1)
          return -6;
          
        event.data.fd = infd;
        event.events = EPOLLIN | EPOLLET;  
        s = epoll_ctl(efd,EPOLL_CTL_ADD,infd,&event);

        if (s == -1)
          return -7;
      }
      else
      {
        size_t count;
        char buf[512];
        while ( 1 )
        {
          count = read(events[i].data.fd,buf,sizeof buf);
          if (count == 0 || count == -1U)
          {
            break;
          }
          write(1,buf,count);
        }
        close(events[i].data.fd);
      }
    }

  }

  close(sfd);
  return 0;
}
